fix: middleware crashes when run is created without a context dict - #1
Merged
Conversation
7 harness tools replacing free-form sandbox tools for constrained tasks: - harness_read_file: tracks files_read, 200-line window with line numbers - harness_search: capped at 30 results with "refine" feedback - harness_propose_patch: validates via linters + git apply --check inline - harness_apply_patch: only if proposal validated - harness_run_tests: structured results, gates commit - harness_commit: blocked unless tests pass - harness_status: shows current state and next valid actions Custom 'harness' agent config with tool_groups: [harness, web]. SOUL.md enforces read→propose→apply→test→commit workflow. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
LangGraph Server runs created without an explicit `context` dict (the
default for the LangGraph SDK, Studio UI, and most external REST callers)
landed in DeerFlow with `runtime.context = None`. Several middlewares
unconditionally called `runtime.context.get(...)`, which raised
`AttributeError: 'NoneType' object has no attribute 'get'` on the very
first hop (`ThreadDataMiddleware.before_agent`) — every external run
failed in 600ms.
This change adds `src/agents/middlewares/_runtime_helpers.py` with two
small helpers:
resolve_runtime_value(runtime, key) -> Any | None
Look up key in runtime.context first; if absent, fall back to
LangGraph-injected config.configurable (where Server puts thread_id
from the URL path). Never raises.
require_thread_id(runtime) -> str
Same lookup but raises ValueError with a meaningful message instead
of AttributeError if the thread_id can't be found anywhere.
The helpers are applied at every call site that previously did the raw
`runtime.context.get("thread_id")` / `runtime.context["thread_id"]` /
`runtime.context.get("sandbox_id")` pattern in middlewares:
backend/src/agents/middlewares/thread_data_middleware.py
backend/src/agents/middlewares/uploads_middleware.py
backend/src/agents/middlewares/memory_middleware.py
backend/src/sandbox/middleware.py
Verified end-to-end: a `lead_agent` run created with only
`{assistant_id, input}` (no context) now completes successfully
("Reply with only the word OK." -> "OK"). Same payload shape failed
in 600ms before the fix.
Other defensive call sites (BudgetEnforcementMiddleware._get_thread_id,
LoopDetectionMiddleware._get_thread_id) already had isinstance guards
and are not touched. Sandbox/tools.py and src/tools/builtins/* still use
the raw pattern; left alone here because they only run when the
middleware chain has already populated thread_id, but they could be
migrated to the helper in a follow-up for consistency.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
External LangGraph callers (SDK, Studio UI, plain REST) typically don't pass a
contextdict on run creation, soruntime.context = None. DeerFlow's middlewares unconditionally calledruntime.context.get(...)and crashed withAttributeError: 'NoneType' object has no attribute 'get'on the very first hop (ThreadDataMiddleware.before_agent). Every external run failed in 600 ms.Fix
New helper module
backend/src/agents/middlewares/_runtime_helpers.py:resolve_runtime_value(runtime, key) -> Any | None— checksruntime.contextfirst, then falls back toconfig.configurable(where LangGraph Server populatesthread_idfrom the URL path). Never raises.require_thread_id(runtime) -> str— same lookup, but raisesValueErrorwith a clear message if the thread_id genuinely can't be found anywhere.Applied at every middleware call site that previously did the raw pattern:
agents/middlewares/thread_data_middleware.pyrequire_thread_id()agents/middlewares/uploads_middleware.pyresolve_runtime_value()agents/middlewares/memory_middleware.pyresolve_runtime_value()sandbox/middleware.pyVerified end-to-end
A
lead_agentrun created with only{assistant_id, input}(no context) now completes successfully —"Reply with only the word OK." → "OK". Same payload shape was crashing in 600 ms before the fix.Out of scope
BudgetEnforcementMiddleware._get_thread_id()andLoopDetectionMiddleware._get_thread_id()already hadisinstance(runtime.context, dict)guards — left alone.sandbox/tools.pyandtools/builtins/*still use the raw pattern. They only run when the middleware chain has populated thread_id, so they're not the bug, but could be migrated to the helper for consistency in a follow-up.🤖 Generated with Claude Code